#!/usr/bin/python
### Based on the Discogs Script by Jesse Ward on
### http://code.google.com/p/jwsandbox/
###
### Interface for Discogs API
### Copyright (C) 2009, Jesse Ward and umat
###
### This program is free software; you can redistribute it and/or
### modify it under the terms of the GNU General Public License
### as published by the Free Software Foundation; either version 2
### of the License, or (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU General Public License for more details.
###
### See: http://www.gnu.org/licenses/gpl-2.0.html

from xml.dom import minidom
import urllib2, gzip, cStringIO, sys, re

API_KEY = "" ## Please create your own API key
URL = "http://www.discogs.com/release/$REL_ID$?f=xml&api_key=$API_KEY$"

class Discogs(object):
    """
    The Discogs class allows you to supply a discogs "release ID", and obtain the 
    following attributes .

        Album artist  : getRelArtist
        Album title   : getRelTitle
        Release year  : getRelYear
        Record label  : getLabel 
        Cat number    : getCatNo
        Track list    : getTrackList
        Genre         : getGenre
        debug         : discDebug

    Discogs requires you to obtain your own API key (http://www.discogs.com/users/api_key). 
    Please see their API wiki (http://www.discogs.com/help/api) for more information.
    Sample code :

    The following code obtains release information for Discogs release 40522,
    Blunted Dummies - House for All

    $ python
    >>> from Discogs import Discogs
    >>> r = Discogs('40522')
    >>> print r.getRelArtist() + '-' + r.getRelTitle()
    Blunted Dummies-House For All

    >>> for c, a, t in r.getTrackList():
        ...     print "%d : %s - %s" % (c, a, t)
        ... 

    1 : Blunted Dummies - House For All (Original Mix)
    2 : Blunted Dummies - House For All (House 4 All Robots Mix)
    3 : Blunted Dummies - House For All (Eddie Richard's Mix)
    4 : Blunted Dummies - House For All (J. Acquaviva's Mix)
    5 : Blunted Dummies - House For All (Ruby Fruit Jungle Mix)

     
    jesse @ housejunkie . ca        
    """

    def __init__(self, relId, build = 1):
        self.relId = relId
        self.url = URL.replace("$REL_ID$", relId, 1)
        self.url = self.url.replace('$API_KEY$', API_KEY,1)
        self.releaseXml = self.loadXml()
        self.relArtist = ''
        self.relTitle = ''
        self.relYear = ''
        self.relLabel = ''
        self.relCatNo = ''
        self.relGenre = ''
        self.relTrackList = []
        self.relImageList = []

        # instanciating Discogs automatically populates all
        # records from the releaseId
        if build:
            self.setRelArtist(self.parseArtists())
            self.setRelTitle(self.parseTitle())
            self.setYear(self.parseYear())
            self.setLabel(self.parseLabel())
            self.setCatNo(self.parseCatNo())
            self.setTrackList(self.parseTrackList())
            self.setImageList(self.parseImageList())
            self.setGenre(self.parseGenre())

    def getReleaseURL(self):
        return self.url

    def loadXml(self):
        """
        fetches a copy of the response XML provided by the discogs
        api. See here http://www.discogs.com/help/api for docs.
        """
        request = urllib2.Request(self.url)
        request.add_header('Accept-Encoding', 'gzip')
        try:
            response = urllib2.urlopen(request)
            data = response.read()
            releaseXml = minidom.parseString(gzip.GzipFile(fileobj = cStringIO.StringIO(data)).read())
        except Exception:
            print "[ERROR] unable to obtain Discogs release : %s" %self.relId
            sys.exit()
        return releaseXml

    def getAllXml(self):
        return self.releaseXml

    def parseLabel(self):
        label = self.releaseXml.getElementsByTagName('label')[0]
        return self.cleanName(label.attributes["name"].value)

    def setLabel(self, label):
        self.relLabel = label

    def getLabel(self):
        return self.relLabel

    def parseCatNo(self):
        catno = self.releaseXml.getElementsByTagName('label')[0]
        return catno.attributes["catno"].value

    def setCatNo(self, catNo):
        self.relCatNo = catNo

    def getCatNo(self):
        return self.relCatNo

    def setRelTitle(self, title):
        self.relTitle = title

    def getRelTitle(self):
        return self.relTitle

    def parseTitle(self, node = None):
        if node == None:
            node = self.releaseXml
        return node.getElementsByTagName('title')[0].firstChild.data

    def parseDuration(self, node = None):
        if node == None:
            node = self.releaseXml
	if not node.getElementsByTagName('duration')[0].firstChild:
	    return None
        return node.getElementsByTagName('duration')[0].firstChild.data

    def parsePosition(self, node = None):
        if node == None:
            node = self.releaseXml
	if not node.getElementsByTagName('position')[0].firstChild:
	    return None
        return node.getElementsByTagName('position')[0].firstChild.data
               
    def setYear(self, year):
        self.relYear = year

    def getRelYear(self):
        return self.relYear

    def parseYear(self, format = 1):
        """
        Parse year of release from xml feed. Currently
        Exits if data is not present. May want to set a
        default value and remove the sys.exit(), if the
        year isn't a requirement for your needs.
        """ 
        try:
            year = self.releaseXml.getElementsByTagName('released')[0].firstChild.data
        except IndexError:
            print "[ERROR] unable to obain release year for : %s" %self.relId
	    return "1980"
        if format:
            return self.cleanYear(year)
        else:
            return year

    def getRelArtist(self):
        return self.relArtist
 
    def setRelArtist(self, artist):
        self.relArtist = artist

    def parseArtists(self, node = None):
        """
        retrieve both the album/release artists
        or the individual artists of each track
        """
        if node == None:
            node = self.releaseXml
        count = 1
        try:
            artists = node.getElementsByTagName('artists')[0]
        except IndexError:
            return self.relArtist
        for nme in artists.getElementsByTagName('name'):
            if count == 1:
                name = nme.firstChild.data
            else:
                name = name + " & " + nme.firstChild.data
            count += 1
        return self.cleanName(name)

    def setTrackList(self, tList):
        self.relTrackList = tList

    def getTrackList(self):
        return self.relTrackList

    def getRelId(self):
        return self.relId

    def parseTrackList(self):
        """
        returns a list (tList), in the following format
        [ [track id, "position", "artist", "track title", "duration"] ] 
        """
        tList = []
        count = 1
        trackList = self.releaseXml.getElementsByTagName('tracklist')[0]
        for tracks in trackList.getElementsByTagName('track'):
            tList.extend([[count, self.parsePosition(tracks), self.parseArtists(tracks), self.parseTitle(tracks), self.parseDuration(tracks)]])
            count += 1
        return tList

    def setImageList(self, iList):
        self.relImageList = iList

    def getImageList(self):
        return self.relImageList

    def parseImageList(self):
        """
        returns a list (iList), in the following format
        [ [image id, "type", "uri"] ] 
        """
        iList = []
        count = 1
        imageList = self.releaseXml.getElementsByTagName('images')[0]
        for image in imageList.getElementsByTagName('image'):
            iList.extend([[count, image.attributes["type"].value, image.attributes["uri"].value]])
            count += 1
        return iList

    def setGenre(self, genre):
        self.relGenre = genre

    def getGenre(self):
        return self.relGenre

    def parseGenre(self):
        """
        Obtains the first <style></style> within the <styles> tag
        """      
	if self.releaseXml.getElementsByTagName('styles'):
    	    styles = self.releaseXml.getElementsByTagName('styles')[0]
    	    style = styles.getElementsByTagName('style')[0].firstChild.data
    	    return style
	elif self.releaseXml.getElementsByTagName('genres'):
    	    genres = self.releaseXml.getElementsByTagName('genres')[0]
    	    genre = genres.getElementsByTagName('genre')[0].firstChild.data
    	    return genre
	return ""

    def cleanName(self, name):
        """
        Cleans up the formatting of artist/label names.
        Discogs orders duplicate names by appending a counter to each dupe
        eg : http://www.discogs.com/search?type=artists&q=goldie&btn=Search
        Goldie
        Goldie (1)
        Goldie (16)
        """
        r = re.compile('\s\(\d+\)')
        return r.sub('', name)

    def cleanYear(self, year):
        """
        Returns the release year in the format : YYYY
        """
        save = re.compile('(\d\d\d\d)')
        return save.match(year).group(1)

    def discDebug(self):
        """
        Debug method to dump all attributes assigned to current Discogs object
        """
        div = "_ _______________________________________________ _ _\n"
        r = div
        r += " Name : %s - %s\n" % (self.relArtist, self.relTitle)
        r += "Label : %s\n" % (self.relLabel)
        r += "Genre : %s\n" % (self.relGenre)
        r += "Catno : %s\n" % (self.relCatNo)
        r += " Year : %s\n" % (self.relYear)
        r += "  URL : http:/www.discogs.com/release/%s\n" % (self.relId)
        r += div
        for cnt, art, trk in self.relTrackList:
            r += "%d %s - %s\n" % (cnt, art, trk)
        r += div
        r += ":: generated by discDebug > questions > jesse@housejunkie.ca\n"
        return r
